#ifndef CNSSHAREDPOINTER_H
#define CNSSHAREDPOINTER_H

#include <utility>
#include <QObject>

template <class T> class CnsSharedPointer;
namespace _CnsSharedPointer {
// helper functions:
    template <class X, class T>
    Q_INLINE_TEMPLATE CnsSharedPointer<X> copyAndSetPointer(X *ptr)
    {
        CnsSharedPointer<X> result;
        result.internalSet(ptr);
        return result;
    }
}

template <class X, class T>
Q_INLINE_TEMPLATE CnsSharedPointer<X> cnsSharedPointerObjectCast(const CnsSharedPointer<T> &src)
{
    X *ptr = qobject_cast<X *>(src.data());
    return _CnsSharedPointer::copyAndSetPointer<X, T>(ptr);
}

template <class X, class T>
Q_INLINE_TEMPLATE CnsSharedPointer<X> cnsSharedPointerConstCast(const CnsSharedPointer<T> &src)
{
    X *ptr = const_cast<X *>(src.data()); // if you get an error in this line, the cast is invalid
    return _CnsSharedPointer::copyAndSetPointer<X, T>(ptr);
}

template <class X, class T>
Q_INLINE_TEMPLATE CnsSharedPointer<X> cnsSharedPointerDynamicCast(const CnsSharedPointer<T> &src)
{
    X *ptr = dynamic_cast<X *>(src.data()); // if you get an error in this line, the cast is invalid
    if (!ptr)
        return CnsSharedPointer<X>();
    return _CnsSharedPointer::copyAndSetPointer<X, T>(ptr);
}

// cast operators
template <class X, class T>
Q_INLINE_TEMPLATE CnsSharedPointer<X> cnsSharedPointerCast(const CnsSharedPointer<T> &src)
{
    X *ptr = static_cast<X *>(src.data()); // if you get an error in this line, the cast is invalid
    return _CnsSharedPointer::copyAndSetPointer<X, T>(ptr);
}

template <class T> class CnsSharedPointer
{
public:
    typedef T Type;

    inline T *data() const noexcept { return value; }
    inline T *get() const noexcept { return value; }
    bool isNull() const noexcept { return !data(); }
    bool operator !() const noexcept { return isNull(); }
    T &operator*() const { return *data(); }
    T *operator->() const noexcept { return data(); }

    constexpr CnsSharedPointer() noexcept : value(nullptr) { }
    ~CnsSharedPointer() { if(value)value->release(); }

    constexpr CnsSharedPointer(std::nullptr_t) noexcept : value(nullptr) { }

    template <class X>
    inline explicit CnsSharedPointer(X *ptr) : value(ptr) // noexcept
    { ptr->retain();}

    template <class X, typename Deleter>
    inline CnsSharedPointer(X *ptr, Deleter deleter) : value(ptr) // throws
    { internalConstruct(ptr, deleter); }

    template <typename Deleter>
    CnsSharedPointer(std::nullptr_t, Deleter) : value(nullptr) { }

    CnsSharedPointer(const CnsSharedPointer &other) noexcept : value(other.value)
    { if(value)value->retain(); }
    CnsSharedPointer &operator=(const CnsSharedPointer &other) Q_DECL_NOTHROW
    {
        CnsSharedPointer copy(other);
        swap(copy);
        return *this;
    }
    CnsSharedPointer(CnsSharedPointer &&other) Q_DECL_NOTHROW
        : value(other.value)
    {
        if(value)value->retain();
        other.value = nullptr;
    }
    CnsSharedPointer &operator=(CnsSharedPointer &&other) Q_DECL_NOTHROW
    {
        CnsSharedPointer moved(std::move(other));
        swap(moved);
        return *this;
    }

    template <class X>
    bool operator==(const CnsSharedPointer<X> &o) const Q_DECL_NOTHROW
    { return value == o.value; }

    template <class X>
    CnsSharedPointer(const CnsSharedPointer<X> &other) noexcept : value(other.value)
    { }

    template <class X>
    inline CnsSharedPointer &operator=(const CnsSharedPointer<X> &other)
    {
        CnsSharedPointer copy(other);
        swap(copy);
        return *this;
    }

    inline void swap(CnsSharedPointer &other)
    { this->internalSwap(other); }

    inline void internalSwap(CnsSharedPointer &other) noexcept
    {
        using std::swap;
        swap(this->value, other.value);
    }

    inline void internalSet(T *actual)
    {
        using std::swap;
        swap(this->value, actual);
    }

    template <class X>
    CnsSharedPointer<X> staticCast() const
    {
        return cnsSharedPointerCast<X, T>(*this);
    }

    template <class X>
    CnsSharedPointer<X> dynamicCast() const
    {
        return cnsSharedPointerDynamicCast<X, T>(*this);
    }

    template <class X>
    CnsSharedPointer<X> constCast() const
    {
        return cnsSharedPointerConstCast<X, T>(*this);
    }

    template <class X>
    CnsSharedPointer<X> objectCast() const
    {
        return cnsSharedPointerObjectCast<X, T>(*this);
    }

    inline void retain()
    {
        value->retain();
    }

    inline void reset() { clear(); }
    inline void reset(T *t)
    { CnsSharedPointer copy(t); swap(copy); }
    template <typename Deleter>
    inline void reset(T *t, Deleter deleter)
    { CnsSharedPointer copy(t, deleter); swap(copy); }

    inline void clear() { CnsSharedPointer copy; swap(copy); }

    template <typename... Args>
    static CnsSharedPointer create(Args && ...arguments)
    {

        CnsSharedPointer result;

        // now initialize the data
        result.value = new T(std::forward<Args>(arguments)...);

        return result;
    }

    Type *value;
};

#endif // CNSSHAREDPOINTER_H
